package com.leap.service.impl;

import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leap.entity.AccountCategoryView;
import com.leap.entity.Article;
import com.leap.entity.Category;
import com.leap.entity.Media;
import com.leap.entity.dto.ArticleDTO;
import com.leap.entity.dto.ArticleUploadDTO;
import com.leap.entity.dto.MediaUploadDTO;
import com.leap.entity.vo.ArticleVO;
import com.leap.entity.vo.HistoryVO;
import com.leap.mapper.ArticleMapper;
import com.leap.mapper.MediaMapper;
import com.leap.service.*;
import com.leap.utils.Const;
import com.leap.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private InteractService interactService;

    @Autowired
    private MediaMapper mediaMapper;

    @Autowired
    private MediaService mediaService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private JwtUtils utils;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public ArticleDTO getInfoByArticleId(Long articleId) {

        LambdaQueryWrapper<Article> articleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        articleLambdaQueryWrapper.eq(Article::getId, articleId);

        Article one = this.getOne(articleLambdaQueryWrapper);
        if(one == null){
            return null;
        }

        List<Media> mediaList = mediaMapper.selectByArticleId(articleId);

        return ArticleDTO.builder()
                .article(one)
                .mediaList(mediaList)
                .build();
    }

    @Override
    public List<ArticleVO> selectByTitile(String title, int category,Long accountId,int page) {

        Page<Article> pageInfo = new Page<>(page,Const.PAGE_SIZE);

        LambdaQueryWrapper<Article> articleQueryWrapper = new LambdaQueryWrapper<>();
        articleQueryWrapper.like(Article::getTitle, title);
        articleQueryWrapper.eq(Article::getIsPublish, 1);

        if (category != 0){
            //有选择分类则加入分类查询条件
        }

        String Like_key = Const.ACCOUNT_LIKE + accountId;
        List<Long> articleLikeList = redisTemplate.opsForList().range(Like_key, 0, -1);

        this.page(pageInfo, articleQueryWrapper);

        List<Article> articleList = pageInfo.getRecords();
        if (articleList.size() == 0){
            throw new StringIndexOutOfBoundsException();
        }

        List<ArticleVO> articleVOList = articleList.stream().map(item -> {
            Long articleId = item.getId();
            Media media = mediaService.getCoverById(articleId);
            if (!articleLikeList.isEmpty()){
                return ArticleVO.builder()
                        .articleId(articleId)
                        .title(item.getTitle()== null ? null : item.getTitle())
                        .category(item.getCategoryId())
                        .coverUrl(media==null ? null : media.getMediaUrl())
                        .browseNum(item.getBrowseNum()==null? 0 : item.getBrowseNum())
                        .likeNum(item.getLikeNum()==null? 0 : item.getLikeNum())
                        .duration(media==null ? 0L : media.getDuration())
                        .isVideo(item.getIsVideo())
                        .isLiked(articleLikeList.contains(articleId) ? 1 : 0)
                        .isPublish(item.getIsPublish())
                        .updateTime(item.getUpdateTime())
                        .build();
            }else {
                return ArticleVO.builder()
                        .articleId(articleId)
                        .title(item.getTitle()== null ? null : item.getTitle())
                        .coverUrl(media==null ? null : media.getMediaUrl())
                        .category(item.getCategoryId())
                        .browseNum(item.getBrowseNum()==null? 0 : item.getBrowseNum())
                        .likeNum(item.getLikeNum()==null? 0 : item.getLikeNum())
                        .duration(media==null ? 0L : media.getDuration())
                        .isVideo(item.getIsVideo())
                        .isLiked(articleLikeList.contains(articleId) ? 1 : 0)
                        .isPublish(item.getIsPublish())
                        .updateTime(item.getUpdateTime())
                        .build();
            }
        }).toList();

        return articleVOList;
    }

    @Override
    public List<ArticleVO> selectByToken(String headerToken,int isVideo) {
        List<Long> articleIdList = historyService.getArticleIdListByToken(headerToken);
        if (articleIdList.isEmpty()){
            return null;
        }
        Long accountId = utils.getAccountIdByToken(headerToken);
        String Like_key = Const.ACCOUNT_LIKE + accountId;
        List<Long> articleLikeList = redisTemplate.opsForList().range(Like_key, 0, -1);

        LambdaQueryWrapper<Article> articleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        articleLambdaQueryWrapper.in(Article::getId, articleIdList);
        articleLambdaQueryWrapper.eq(Article::getIsPublish, 1);
        articleLambdaQueryWrapper.eq(Article::getIsVideo, isVideo);

        List<Article> articleList = this.list(articleLambdaQueryWrapper);
        if (articleList.size() == 0){
            throw new StringIndexOutOfBoundsException();
        }

        List<ArticleVO> articleVOList = articleList.stream().map(item -> {
            Long articleId = item.getId();
            Media media = mediaService.getCoverById(articleId);
            if (!articleLikeList.isEmpty()){
                return ArticleVO.builder()
                        .articleId(articleId)
                        .title(item.getTitle())
                        .category(item.getCategoryId())
                        .coverUrl(media==null ? null : media.getMediaUrl())
                        .browseNum(item.getBrowseNum())
                        .likeNum(item.getLikeNum())
                        .duration(media==null ? 0L : media.getDuration())
                        .isVideo(item.getIsVideo())
                        .isLiked(articleLikeList.contains(articleId) ? 1 : 0)
                        .isPublish(item.getIsPublish())
                        .updateTime(item.getUpdateTime())
                        .build();
            }else {
                return ArticleVO.builder()
                        .articleId(articleId)
                        .title(item.getTitle())
                        .category(item.getCategoryId())
                        .coverUrl(media==null ? null : media.getMediaUrl())
                        .browseNum(item.getBrowseNum())
                        .likeNum(item.getLikeNum())
                        .duration(media==null ? 0L : media.getDuration())
                        .isVideo(item.getIsVideo())
                        .isLiked(0)
                        .isPublish(item.getIsPublish())
                        .updateTime(item.getUpdateTime())
                        .build();
            }
        }).toList();

        return articleVOList;
    }

    @Override
    public ArticleUploadDTO saveDTO(ArticleUploadDTO articleDTO) {
        cssDeal(articleDTO);

        Article article = Article.builder()
                .html(articleDTO.getHtml())
                .css(articleDTO.getCss())
                .title(articleDTO.getTitle())
                .authorName(articleDTO.getAuthor())
                .categoryId(articleDTO.getCategory())
                .isVideo(articleDTO.getIsVideo())
                .browseNum(0)
                .likeNum(0)
                .isPublish(0)
                .build();
        log.info("article_save_before : {}",article);

        articleMapper.insert(article);
        Long articleId = article.getId();
        articleDTO.setArticleId(articleId);
        log.info("article_save_after : {}",article);

        return articleDTO;
    }

    private static void cssDeal(ArticleUploadDTO articleDTO) {
        List<String> cssList = articleDTO.getCss();
        for (String str : cssList) {
            str.replace("[", "");
            str.replace("]", "");
        }
        log.info("css_deal: {}", cssList);
    }


    @Override
    public void updateDTO(ArticleUploadDTO articleDTO) {
        cssDeal(articleDTO);

        String title = articleDTO.getTitle();
        Article article = Article.builder()
                .id(articleDTO.getArticleId())
                .title(title)
                .html(articleDTO.getHtml())
                .css(articleDTO.getCss())
                .authorName(articleDTO.getAuthor())
                .categoryId(articleDTO.getCategory())
                .isVideo(articleDTO.getIsVideo())
                .isPublish(articleDTO.getIsPublish())
                .build();

        log.info("article_update_before : {}",article);

        this.saveOrUpdate(article);

        Long articleId = article.getId();

        mediaService.deleteMedia(articleId);
        mediaService.saveMedia(articleDTO, articleId);

    }

    @Override
    public void deleteDTO(Long articleId) {
        mediaService.deleteMedia(articleId);
        this.removeById(articleId);
    }

    @Override
    public void saveHistory(String title, String headerToken) {
        Long id = utils.getAccountIdByToken(headerToken);

        int count = articleMapper.selectSearchHistory(title,id);
        if (count == 0 ){
            articleMapper.saveSearchHistory(title,id, LocalDateTime.now());
        }else{
            articleMapper.updateSearchHistory(title,id,LocalDateTime.now());
        }
    }

    @Override
    public List<HistoryVO> getHistory(String headerToken) {
        Long id = utils.getAccountIdByToken(headerToken);
        List<HistoryVO> historyVOList = articleMapper.selectHistoryById(id);

        return historyVOList;
    }


    @Override
    public ArticleUploadDTO getUploadDTOByArticleId(Long articleId) {
        ArticleDTO articleDTO = this.getInfoByArticleId(articleId);

        List<Media> mediaList = articleDTO.getMediaList();
        List<MediaUploadDTO> mediaUploadDTOList = new ArrayList<>();
        for (Media media : mediaList) {
            MediaUploadDTO mediaUploadDTO = MediaUploadDTO.builder()
                    .mediaType(media.getMediaType())
                    .fileUrl(media.getMediaUrl() == null ? null : media.getMediaUrl())
                    .duration(media.getDuration())
                    .build();
            mediaUploadDTOList.add(mediaUploadDTO);
        }

        return ArticleUploadDTO.builder()
                .articleId(articleId)
                .title(articleDTO.getArticle().getTitle()==null ? null : articleDTO.getArticle().getTitle())
                .author(articleDTO.getArticle().getAuthorName()==null ? null : articleDTO.getArticle().getAuthorName())
                .html(articleDTO.getArticle().getHtml()==null ? null : articleDTO.getArticle().getHtml())
                .css(articleDTO.getArticle().getCss()==null ? null : articleDTO.getArticle().getCss())
                .category(articleDTO.getArticle().getCategoryId())
                .isVideo(articleDTO.getArticle().getIsVideo())
                .mediaUrl(mediaUploadDTOList)
                .isPublish(articleDTO.getArticle().getIsPublish())
                .build();
    }

    @Override
    public void countCategoryView(Long articleId, String headerToken) {
        Long id = utils.getAccountIdByToken(headerToken);
        int categoryId = articleMapper.selectCategoryIdByArticleId(articleId);
        String key = Const.ACCOUNT_CATEGORY_VIEW + id;

        int viewCounts = redisTemplate.opsForHash().increment(key, Integer.toString(categoryId), 1).intValue();

        //消息队列 异步记录到MySQL
        rabbitTemplate.convertAndSend("amq.direct", "my-categoryView",
                AccountCategoryView.builder()
                        .accountId(id)
                        .categoryId(categoryId)
                        .viewCount(viewCounts)
                        .build()
        );

    }

    @Override
    public void clearRedisCache() {
        String articleSum = Const.ARTICLE_SUM;
        int sum = (int) redisTemplate.opsForValue().get(articleSum);
        if (sum == 0) {
            this.refreshArticleSumCache();
        }
        int Page = sum / Const.PAGE_SIZE;
        for (int i = 0; i <= Page; i++) {
            //删除首页最新文章集合的缓存
            String listNew = Const.FIRST_NEW + i + "*";
            Set listNewKey = redisTemplate.keys(listNew);
            redisTemplate.delete(listNewKey);
            //删除首页文章集合
            String first = Const.FIRST_PAGE + i + "*";
            Set firstKey = redisTemplate.keys(first);
            redisTemplate.delete(firstKey);
            for (int j = 0; j < Const.PAGE_SIZE; j++) {
                //删除首页热门和视频文章集合的缓存
                String firstHotVideo = Const.FIRST_VIDEO_OR_HOT + i + "_" + j + "*";
                Set firstHotVideoKey = redisTemplate.keys(firstHotVideo);
                redisTemplate.delete(firstHotVideoKey);

            }
        }
        String categoryName = Const.CATEGORY_NAME;
        List<Category> categoryNames = redisTemplate.opsForList().range(categoryName, 0, -1);
        if (categoryNames.isEmpty()){
            categoryService.loadCategoryName();
        }
        //删除按照分类获取热门文章集合的缓存
        int size = categoryNames.size();
        for (int i = 0; i < Page; i++) {
            for (int j = 0; j < size; j++) {
                String categoryList = Const.CATEGORY_LIST + i + "_" + j + "*";
                Set categoryKey = redisTemplate.keys(categoryList);
                redisTemplate.delete(categoryKey);
            }
        }
    }

    @Override
    public void refreshArticleSumCache() {
        // 已发布文章 统计缓存
        String key = Const.ARTICLE_SUM;
        String key2 = Const.VIDEO_ARTICLE_SUM;
        //各个分类文章缓存的KEY
        String category1 = Const.CATEGORY_1;
        String category2 = Const.CATEGORY_2;
        String category3 = Const.CATEGORY_3;
        String category4 = Const.CATEGORY_4;
        String category1_v = Const.CATEGORY_1_VIDEO;
        String category2_v = Const.CATEGORY_2_VIDEO;
        String category3_v = Const.CATEGORY_3_VIDEO;
        String category4_v = Const.CATEGORY_4_VIDEO;

        int sum = articleMapper.getArticleSum();
        int videoSum = articleMapper.getVideoArticleSum();
        //各个分类文章总数
        int c1 = articleMapper.getCategoryArticleSum(1,0);
        int c2 = articleMapper.getCategoryArticleSum(2,0);
        int c3 = articleMapper.getCategoryArticleSum(3,0);
        int c4 = articleMapper.getCategoryArticleSum(4,0);

        int cv1 = articleMapper.getCategoryArticleSum(1,1);
        int cv2 = articleMapper.getCategoryArticleSum(2,1);
        int cv3 = articleMapper.getCategoryArticleSum(3,1);
        int cv4 = articleMapper.getCategoryArticleSum(4,1);

        Set keys = redisTemplate.keys(key);
        Set keys2 = redisTemplate.keys(key2);

        //各个分类文章keys
        Set ckey1 = redisTemplate.keys(category1);
        Set ckey2 = redisTemplate.keys(category2);
        Set ckey3 = redisTemplate.keys(category3);
        Set ckey4 = redisTemplate.keys(category4);
        Set cv1key = redisTemplate.keys(category1_v);
        Set cv2key = redisTemplate.keys(category2_v);
        Set cv3key = redisTemplate.keys(category3_v);
        Set cv4key = redisTemplate.keys(category4_v);

        redisTemplate.delete(keys);
        redisTemplate.delete(keys2);
        redisTemplate.delete(ckey1);
        redisTemplate.delete(ckey2);
        redisTemplate.delete(ckey3);
        redisTemplate.delete(ckey4);
        redisTemplate.delete(cv1key);
        redisTemplate.delete(cv2key);
        redisTemplate.delete(cv3key);
        redisTemplate.delete(cv4key);

        redisTemplate.opsForValue().set(key,sum);
        redisTemplate.opsForValue().set(key2,videoSum);
        redisTemplate.opsForValue().set(category1, c1);
        redisTemplate.opsForValue().set(category2, c2);
        redisTemplate.opsForValue().set(category3, c3);
        redisTemplate.opsForValue().set(category4, c4);

        redisTemplate.opsForValue().set(category1_v, cv1);
        redisTemplate.opsForValue().set(category2_v, cv2);
        redisTemplate.opsForValue().set(category3_v, cv3);
        redisTemplate.opsForValue().set(category4_v, cv4);

        redisTemplate.expire(key, 2, TimeUnit.DAYS);
        redisTemplate.expire(key2, 2, TimeUnit.DAYS);

        redisTemplate.expire(category1, 2, TimeUnit.DAYS);
        redisTemplate.expire(category2, 2, TimeUnit.DAYS);
        redisTemplate.expire(category3, 2, TimeUnit.DAYS);
        redisTemplate.expire(category4, 2, TimeUnit.DAYS);

        redisTemplate.expire(category1_v, 2, TimeUnit.DAYS);
        redisTemplate.expire(category2_v, 2, TimeUnit.DAYS);
        redisTemplate.expire(category3_v, 2, TimeUnit.DAYS);
        redisTemplate.expire(category4_v, 2, TimeUnit.DAYS);

        // 未发布文章 统计缓存
        String nokey1 = Const.NOT_PUBLISH_ARTICLE_SUM;
        String nokey2 = Const.NOT_PUBLISH_VIDEO_ARTICLE_SUM;
        //四个分类 缓存key
        String n_category1_article = Const.NOT_PUBLISH_CATEGORY_1_ARTICLE_SUM;
        String n_category1_video = Const.NOT_PUBLISH_CATEGORY_1_VIDEO_SUM;
        String n_category2_article = Const.NOT_PUBLISH_CATEGORY_2_ARTICLE_SUM;
        String n_category2_video = Const.NOT_PUBLISH_CATEGORY_2_VIDEO_SUM;
        String n_category3_article = Const.NOT_PUBLISH_CATEGORY_3_ARTICLE_SUM;
        String n_category3_video = Const.NOT_PUBLISH_CATEGORY_3_VIDEO_SUM;
        String n_category4_article = Const.NOT_PUBLISH_CATEGORY_4_ARTICLE_SUM;
        String n_category4_video = Const.NOT_PUBLISH_CATEGORY_4_VIDEO_SUM;

        int na = articleMapper.getIsNotPublishSum();
        int nv = articleMapper.getIsNotPublishVideoSum();
        int nca1 = articleMapper.getNotPublishCategoryArticleSum(1,0);
        int nca2 = articleMapper.getNotPublishCategoryArticleSum(2,0);
        int nca3 = articleMapper.getNotPublishCategoryArticleSum(3,0);
        int nca4 = articleMapper.getNotPublishCategoryArticleSum(4,0);

        int ncv1 = articleMapper.getNotPublishCategoryArticleSum(1,1);
        int ncv2 = articleMapper.getNotPublishCategoryArticleSum(2,1);
        int ncv3 = articleMapper.getNotPublishCategoryArticleSum(3,1);
        int ncv4 = articleMapper.getNotPublishCategoryArticleSum(4,1);

        //获取各个keys
        Set nakey = redisTemplate.keys(nokey1);
        Set nvkey = redisTemplate.keys(nokey2);

        Set nca1key = redisTemplate.keys(n_category1_article);
        Set nca2key = redisTemplate.keys(n_category2_article);
        Set nca3key = redisTemplate.keys(n_category3_article);
        Set nca4key = redisTemplate.keys(n_category4_article);

        Set ncv1key = redisTemplate.keys(n_category1_video);
        Set ncv2key = redisTemplate.keys(n_category2_video);
        Set ncv3key = redisTemplate.keys(n_category3_video);
        Set ncv4key = redisTemplate.keys(n_category4_video);

        redisTemplate.delete(nakey);
        redisTemplate.delete(nvkey);
        redisTemplate.delete(nca1key);
        redisTemplate.delete(nca2key);
        redisTemplate.delete(nca3key);
        redisTemplate.delete(nca4key);

        redisTemplate.delete(ncv1key);
        redisTemplate.delete(ncv2key);
        redisTemplate.delete(ncv3key);
        redisTemplate.delete(ncv4key);

        redisTemplate.opsForValue().set(nokey1, na);
        redisTemplate.opsForValue().set(nokey2, nv);

        redisTemplate.opsForValue().set(n_category1_article, nca1);
        redisTemplate.opsForValue().set(n_category2_article, nca2);
        redisTemplate.opsForValue().set(n_category3_article, nca3);
        redisTemplate.opsForValue().set(n_category4_article, nca4);

        redisTemplate.opsForValue().set(n_category1_video, ncv1);
        redisTemplate.opsForValue().set(n_category2_video, ncv2);
        redisTemplate.opsForValue().set(n_category3_video, ncv3);
        redisTemplate.opsForValue().set(n_category4_video, ncv4);

        //设置过期时间
        redisTemplate.expire(nokey1, 1, TimeUnit.DAYS);
        redisTemplate.expire(nokey2, 1, TimeUnit.DAYS);

        redisTemplate.expire(n_category1_article, 1, TimeUnit.DAYS);
        redisTemplate.expire(n_category2_article, 1, TimeUnit.DAYS);
        redisTemplate.expire(n_category3_article, 1, TimeUnit.DAYS);
        redisTemplate.expire(n_category4_article, 1, TimeUnit.DAYS);

        redisTemplate.expire(n_category1_video, 1, TimeUnit.DAYS);
        redisTemplate.expire(n_category2_video, 1, TimeUnit.DAYS);
        redisTemplate.expire(n_category3_video, 1, TimeUnit.DAYS);
        redisTemplate.expire(n_category4_video, 1, TimeUnit.DAYS);

    }

    @Override
    public void deleteAllInteract(Long articleId) {
        interactService.deleteAllInteract(articleId);
    }

    @Override
    public void increseBrowseNum(Long articleId) {
        articleMapper.addOneBrowseNum(articleId);
    }

}
